<?php
/*
 * @Author: your name
 * @Date: 2020-12-01 16:12:26
 * @LastEditTime: 2021-01-14 17:42:32
 * @LastEditors: Please set LastEditors
 * @Description: 防火墙规则排序类
 * @FilePath: /special/application/adminapiv3/service/module_manage/netfilter/SortRuleService.php
 */
namespace app\adminapiv3\service\module_manage\netfilter;

use app\adminapiv3\service\BaseService;
use app\common\definition\HostDefinition;
use app\common\file_path\NetfilterFilePath;
use app\common\definition\netfilter\FirewallRuleDefinition;
use think\Db;

class SortRuleService extends BaseService
{
    /**
     * @name: 推送规则内容到排序组件 排序规则
     * @Param: 
     * @Description: 因为排序组件不属于PHP,所以需要推送到本地的排序组件,
     *               排序组件是通过扫描对应文件夹来读取内容,所以需要写入规则内容到指定文件中
     * @Author: foo
     * @Date: 2020-11-30 14:35:37
     */
    public function pushContentToFile ($host_id)
    {
        /* 
            外网模式 0 为黑名单模式 (黑名单阻止) , 1为白名单模式 (白名单允许).
            当前规则还没有 黑白名单模式 所以默认为 0 2020-11-30
        */
        $return['exmode'] = 0;

        //查询内网ip池
        $intranet = Db::name('assest_intranet_ip')->alias('aii')->field('aii.*')->join('host h', 'h.address_id = aii.address_id')->where('h.id', $host_id)->select();
        $return['ippool'] = array_map(function($ip){
            if($ip['ip_s'] === $ip['ip_e'])
            {
                return $ip['ip_s'];
            }
            return $ip['ip_s'] . '-' . $ip['ip_s'];
        }, $intranet);

        //客户端网卡的标示外网ip
        $return['extranetip'] = [];
        //客户端网卡的标示内网ip
        $return['intranetip'] = [];
        //规则组
        $return['iptrules'] = Db::name('netfilter_firewall_rule')->field('id,region,content')->where('host_id', $host_id)->select();
        //重置状态 为等待排序
        Db::name('host_learn_config')->where('host_id', $host_id)->update(['current_rule_group_status' => FirewallRuleDefinition::WATTING_FOR_SORT_RULE_GROUP_STATUS]);
        
        //写入文件等待排序组件调用
        file_put_contents( NetfilterFilePath::WAITING_FOR_SORT . $host_id . '.rules', json_encode($return));
        
    }

    /**
     * @name: 添加日志规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-02 10:56:20
     */
    public function addLogRule ($host_id, $data)
    {
        $log_status = Db::name('host_learn_config')->where('host_id', $host_id)->value('log_status');

        if (!$log_status)
        {
            return $data;
        }

        $firewall_config = Db::name('netfilter_firewall_config_linux')->where('host_id', $host_id)->find();
        
        if(empty($firewall_config))
        {
            return $data;
        }
        
        $return = [];
        $log_content = '-j LOG --log-prefix "netfilter " --log-level 7';

        foreach ($data as $rule)
        {
            $preg_content = preg_replace('/-j\s+(DROP|REJECT).*?$/i', $log_content, $rule['content'], -1, $count);
            if($count)
            {
                $return[] = [
                    'id' => 0,
                    'rule_id' => 0,
                    'host_id' => $host_id,
                    'content' => $preg_content,
                    'rank' =>  0,
                    'source' => $rule['source'],
                    'region' => $rule['region'],
                    'status' => $rule['status'],
                ];
            }
            
            $return[] = $rule;
        }

        //插入出站日志规则在 -P 之后
        $out_log_rule = [
            'id' => 0,
            'rule_id' => 0,
            'host_id' => $host_id,
            'content' => '-A OUTPUT ' . $log_content,
            'rank' =>  0,
            'source' => FirewallRuleDefinition::FIREWALL_LOG_RULE_SOURCE,
            'region' => FirewallRuleDefinition::CONNECT_TOP_RULE_REGION,
        ];
        array_splice($return, 3, 0, [$out_log_rule]);
        
        if (!$firewall_config['input'])
        {
            $return[] = [
                'id' => 0,
                'rule_id' => 0,
                'host_id' => $host_id,
                'content' =>  '-A INPUT ' . $log_content,
                'rank' =>  9999,
                'source' => FirewallRuleDefinition::FIREWALL_LOG_RULE_SOURCE,
                'region' => FirewallRuleDefinition::DEFAULT_TAIL_RULE_REGION,
            ];
        }
        
        return $return;
    }

     /**
     * @name: 获取端口伪装规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-25 10:13:37
     */
    public function addDisguisePortRule ($host_id, $host_os, $data)
    {
        if (Db::name('host_guise_config')->where('host_id', $host_id)->value('guise_status'))
        {
            $disguise_port = Db::name('host_guise_port')->field('protocols,port')->where('host_id', $host_id)->select();
            if($host_os == HostDefinition::WINDOWS_OPERATION_SYSTEM)
            {
                $str = 'netsh advfirewall firewall add rule name="disguise_port_rule" protocol=%s dir=in localport=%u action=allow';
            }
            else
            {
                $str = '-A INPUT -p %s -m multiport  --dports %u -j ACCEPT';
            }
            
            $protocols_a = [
                6 => 'tcp',
                17 => 'udp',
                1 => 'icmp',
            ];

            foreach ($disguise_port as $port)
            {
                $disguise_port_rule[] = [
                    'id' => 0,
                    'rule_id' => 0,
                    'host_id' => $host_id,
                    'content' => sprintf($str, $protocols_a[$port['protocols']], $port['port']) ,
                    'rank' =>  0,
                    'source' => FirewallRuleDefinition::DISGUISE_PORT_RULE_SOURCE,
                    'region' => FirewallRuleDefinition::DEFAULT_NORMAL_RULE_REGION,
                    'status' => FirewallRuleDefinition::RULE_ENABLE,
                ];
            }
            $data = array_merge($data, $disguise_port_rule);
        }

        return $data;
    }

    /**
     * @name: 排序规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-12 15:06:23
     */
    public function sortRule ($data)
    {
        $p_input_rule = $p_forward_rule = $p_output_rule = [];
        $a_input_rule = $a_forward_rule = $a_output_rule = $a_other_rule = [];
        $n_rule = [];
        foreach ($data as $key => $rule)
        {
            switch ($rule['content'])
            {
                case preg_match('/^-P\sINPUT.*?$/i', $rule['content'])  == 1:
                    $p_input_rule[] =  [
                        'content' => $rule['content'],
                        'rank' => $rule['rank'],
                        'key' => $key,
                    ];
                    $data[$key]['rank'] = 0;
                break;
                case preg_match('/^-P\sFORWARD.*?$/i', $rule['content'])  == 1:
                    $p_forward_rule[] =  [
                        'content' => $rule['content'],
                        'rank' => $rule['rank'],
                        'key' => $key,
                    ];
                    $data[$key]['rank'] = 0;
                break;
                case preg_match('/^-P\sOUTPUT.*?$/i', $rule['content'])  == 1:
                    $p_output_rule[] =  [
                        'content' => $rule['content'],
                        'rank' => $rule['rank'],
                        'key' => $key,
                    ];
                    $data[$key]['rank'] = 0;
                break;
                case preg_match('/^-N.*?$/i', $rule['content']) == 1:
                    $n_rule[] =  [
                        'content' => $rule['content'],
                        'rank' => $rule['rank'],
                        'key' => $key,
                    ];
                break;
                case preg_match('/^-A\sINPUT.*?$/i', $rule['content']) == 1:
                    $a_input_rule[] =  [
                        'content' => $rule['content'],
                        'rank' => $rule['rank'],
                        'key' => $key,
                    ];
                break;
                case preg_match('/^-A\sFORWARD.*?$/i', $rule['content']) == 1:
                    $a_forward_rule[] = [
                        'content' => $rule['content'],
                        'rank' => $rule['rank'],
                        'key' => $key,
                    ];
                break;
                case preg_match('/^-A\sOUTPUT.*?$/i', $rule['content']) == 1:
                    $a_output_rule[] = [
                        'content' => $rule['content'],
                        'rank' => $rule['rank'],
                        'key' => $key,
                    ];
                break;
                case preg_match('/^-A.*?$/i', $rule['content']) == 1:
                    $a_other_rule[] = [
                        'content' => $rule['content'],
                        'rank' => $rule['rank'],
                        'key' => $key,
                    ];
                break;
            }
        }
        
        $this->rankSort($a_input_rule);
        $this->rankSort($a_forward_rule);
        $this->rankSort($a_output_rule);
        $this->rankSort($a_other_rule);
        $this->rankSort($n_rule);

        $sort_rule = array_merge($p_input_rule, $p_forward_rule, $p_output_rule, $n_rule, $a_input_rule, $a_forward_rule, $a_output_rule, $a_other_rule);
        $return = [];
        foreach ($sort_rule as $key => $value)
        {
            $return[] = $data[$value['key']];
        }

        return $return;
    }

    /**
     * @name: 按照排序调整
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-29 13:57:25
     * @param {*} array
     */
    private function rankSort(array &$rule)
    {
        uasort($rule, function($a, $b)
        {
            if($b['rank'] == 0)
            {
                return -1;
            }
            
            if ($a['rank'] > $b['rank'])
            {
                return 1;
            }
            else
            {
                return -1;
            }
        });
    }
}